#include "cmds.h"
#include <tchar.h>

std::map<CmdType, tstr> GCmdNames;

int GCmdCounter[UnknownCmd + 1] = { 0 };

void init_cmdnames_map()
{
	GCmdNames[CopyTo]     = _T ("CopyTo");
	GCmdNames[Move]       = _T ("Move");
	GCmdNames[Delete]     = _T ("Delete");
	GCmdNames[Rename]     = _T ("Rename");
	GCmdNames[CmdObject]  = _T ("CmdObject");
	GCmdNames[UnknownCmd] = _T ("UnknownCmd");
}

int GetCmdCounter (CmdType _type)
{
	return GCmdCounter[_type]++;
}

tstr CreateDefaultCmdDescription (CmdType _type)
{
	static tostrstream toss;
	toss << GCmdNames[_type] << _T ('_') << GetCmdCounter (_type);

	tstr ret (toss.str ());
	toss.str (_T (""));
	toss.clear ();
	return ret;
}

FFileNode::FFileNode () :is_ending (false)
{

}

void FFileNode::add (tcstr& path)
{
	if (!path.empty ())
	{
		LPCTSTR pChar        = path.c_str ();
		LPCTSTR pStart       = pChar;
		FFileNode* child_ptr = this;

		TCHAR ch;
		for (++pChar; (ch = *pChar) != 0; ++pChar)
		{
			if (ch == _T('/') || ch == _T('\\'))
			{
				child_ptr    = &(child_ptr->NameMap[tstr (pStart, pChar)]);
				//child_ptr  = &(child_ptr->NameMap[tstr(1, ch)]);
				child_ptr    = &(child_ptr->NameMap[_T ("\\")]);
				pStart       = pChar + 1;
				if (*pStart != 0)
				{
					++pChar;
				}
				else {
					child_ptr->is_ending = true;
				}
			}
		}
		if (*pStart != 0)
		{
			child_ptr = &(child_ptr->NameMap[tstr (pStart, pChar)]);
			child_ptr->is_ending = true;
		}
	}
}

std::vector<TCHAR> FFileNode::build_paths ()
{
	std::vector<TCHAR> ret;
	if (NameMap.empty ()) {
		return ret;
	}

	using NameMapIter = std::map<tstr, FFileNode>::iterator;
	using ContextItem = std::pair<FFileNode*, NameMapIter>;

	std::vector<ContextItem> context (1, { this, NameMap.begin () });

	auto step = [&context, &ret] ()
	{
		ContextItem& item       = context.back ();
		FFileNode* current_node = item.first;
		NameMapIter& iter       = item.second;

		if (iter == current_node->NameMap.end ())
		{
			context.pop_back ();
			if (!context.empty ())
			{
				ContextItem& p     = context.back ();
				NameMapIter& i = p.second;
				++i;
			}
		}
		else {
			if (!iter->second.NameMap.empty())
			{
				if (iter->second.is_ending) {
					tstr path;
					for (auto i = context.begin (); i != context.end (); ++i)
					{
						//path.append (i->second->first);
						NameMapIter p = i->second;
						path.append (p->first);
					}
					ret.insert (ret.end (), path.begin (), path.end ());
					ret.push_back (_T ('\0'));
				}

				context.push_back ({ &iter->second, iter->second.NameMap.begin () });
			}
			else {
				tstr path;
				for (auto i = context.begin (); i != context.end (); ++i)
				{
					//path.append (i->second->first);
					NameMapIter p = i->second;
					path.append (p->first);
				}
				ret.insert (ret.end (), path.begin (), path.end ());
				ret.push_back (_T ('\0'));

				++iter;
			}
		}
	};

	while (!context.empty ())
	{
		step ();
	}

	ret.push_back (_T ('\0'));

	return ret;
}

void FCmdFilePathMgr::add (tcstr& path)
{
	m_node.add (path);
}

std::vector<TCHAR> FCmdFilePathMgr::build_paths ()
{
	return m_node.build_paths ();
}

FCmdBase::FCmdBase (CmdType _type, tcstr& _descriptor/* = _T ("")*/)
	: m_type (_type), m_desc (_descriptor)
{
	if (m_desc.empty ())
	{
		m_desc = CreateDefaultCmdDescription (_type);
	}

	switch (_type)
	{
	case CopyTo:
		m_wFunc = FO_COPY;
		break;
	case Move:
		m_wFunc = FO_MOVE;
		break;
	case Delete:
		m_wFunc = FO_DELETE;
		break;
	case Rename:
		m_wFunc = FO_RENAME;
		break;
	default:
		m_wFunc = -1;
		break;
	}

	m_fFlags = FOF_ALLOWUNDO | FOF_FILESONLY | FOF_NOCONFIRMMKDIR /*| FOF_RENAMEONCOLLISION*/;
}

FCmdBasePtr FCmdBase::copy()
{
	FCmdBasePtr new_obj (new FCmdBase (m_type, m_desc + _T("_copy")));
	new_obj->m_from   = m_from;
	new_obj->m_to     = m_to;
	new_obj->m_wFunc  = m_wFunc;
	new_obj->m_fFlags = m_fFlags;
	return new_obj;
}

CmdResult FCmdBase::run ()
{
	std::vector<TCHAR> from = m_from.build_paths ();
	std::vector<TCHAR> to   = m_to.build_paths ();

	SHFILEOPSTRUCT op;
	op.wFunc  = m_wFunc;
	op.fFlags = m_fFlags;
	op.pFrom  = from.data ();
	op.pTo    = to.data ();

	int iRet = SHFileOperation (&op);
	if (iRet == 0)
	{
		if (op.fAnyOperationsAborted != TRUE)
		{
			return AllOk;
		}
		else
		{
			return Canceled;
		}
	}

	/*switch (iRet) {
	case DE_OPCANCELLED:
		break;
	}*/
	return SomeError;
}

void FCmdBase::dump (int nLevel)
{
	for (int i = 0; i < nLevel; ++i)
	{
		GLog << _T ('\t');
	}
	GLog << _T (' ') << m_desc << std::endl;
}

void FCmdBase::setDescription (tcstr _desc)
{
	m_desc = _desc;
}

bool FComposeCmd::addCmd (const FCmdBasePtr& cmd)
{
	m_cmds.push_back (cmd);
	GLog << m_desc << _T (" add ") << cmd->m_desc << _T (" succeed!") << std::endl;;
	return true;
}

FCmdBasePtr FComposeCmd::copy()
{
	FComposeCmdPtr new_obj (new FComposeCmd (m_desc + _T ("_copy")));
	for (auto i = m_cmds.begin (); i != m_cmds.end (); i++) {
		new_obj->m_cmds.push_back ((*i)->copy ());
	}
	return new_obj;
}

CmdResult FComposeCmd::run ()
{
	CmdResult ret = AllOk;
	for (auto it = m_cmds.begin (); it != m_cmds.end (); ++it)
	{
		CmdResult tmp = (*it)->run ();
		if (tmp != AllOk)
		{
			if (tmp == Canceled)
			{
				return tmp;
			}
			ret = SomeError;
		}
	}
	return ret;
}

void FComposeCmd::dump (int nLevel /*= 0*/)
{
	for (int i = 0; i < nLevel; ++i)
	{
		GLog << _T ('\t');
	}

	GLog << _T (' ') << m_desc << std::endl;

	for (auto i = m_cmds.begin (); i != m_cmds.end (); ++i)
	{
		(*i)->dump (nLevel + 1);
	}
}

